/*
 * $Id$
 */
package corejs.rt;

public abstract class Invocable extends Obj {

   // -- Function.prototype ---
   public static final Value prototype = new Invocable() {
      @Override
      public Value invoke(final ExecutionContext ec) {
         return null;
      }
   };  
   
   static final void _fn_init_prototype_() {
      
      prototype._proto = Obj.prototype;
      
      // -- Function.prototype.apply 
      prototype.store("apply", new F2("apply") {
         @Override
         public Value invoke(final ExecutionContext ec) {
            final Invocable fn = ec.get(Invocable.class);
            final Value args = ec.get(_arg2_);
            ec.put("this", ec.get(_arg1_));
            final Array argsArray = args.isUndefined() ? new Array() : (Array)args;
            final String[] argNames = fn.getArgumentNames();
            for (int i = 0; i < argNames.length; ++i) {
               ec.put(argNames[i],  i < argsArray.length() ? argsArray.resolve(i)
                                                           : Undefined.UNDEFINED);
            }
            return fn.invoke(ec);
         }
      });      

      // -- Function.prototype.bind 
      prototype.store("bind", new F1("bind") {
         @Override
         public Value invoke(final ExecutionContext ec) {
            final Invocable fn = ec.get(Invocable.class);
            final Value thisArg = ec.get(_arg_);
            return new Function(prototype) {
               @Override
               public String[] getArgumentNames() {return fn.getArgumentNames();}
 
               @Override
               public Value invoke(final ExecutionContext ec) {
                  ec.put("this", thisArg);
                  return fn.invoke(ec);
               }
            };
         }
      });
     
      // -- Function.prototype.call
      final String[] _call_args_ = {"!thisArg!", "*"};
      prototype.store("call", new Invocable("call") {
         @Override
         public String[] getArgumentNames() {return _call_args_;}

         @Override
         public Value invoke(final ExecutionContext ec) {
            final Invocable fn = ec.get(Invocable.class);
            final String[] argNames = fn.getArgumentNames();
            final Arguments arguments = ec.get("arguments", Arguments.class);
            for (int i = 0; i < argNames.length; ) {
               ec.put(argNames[i], arguments.resolve(++i));
            }
            ec.put("this", ec.get("!thisArg!"));
            return fn.invoke(ec);
         }
      });

      // -- Function.prototype.toString
      prototype.store("toString", new Invocable("toString"){
         @Override      
         public Value invoke(final ExecutionContext ec) {
            return new Str(new StringBuilder("function ")
                           .append(ec.get("this").resolve("name"))
                           .append("() {\n    [native code]\n}"));
         }
      });
   }
   
   // --
   public Invocable() {
      this("");
   }
   
   public Invocable(final String name) {
      super(prototype);
      store("name", new Str(name));
   }
   
   // -- typeof ----
   @Override
   public Str typeof() {return Str.FUNCTION;}

   /**
    * The direct implementation of {@code Invocable} takes no arguments.
    */
   private final static String[] _void_arguments_ = {};
   public String[] getArgumentNames() { return _void_arguments_; }

   /**
    *
    * @return
    */
   public ActivationRecord parentScope() {
      return null;
   };

   /**
    * Invokes this function in the given {@code ExecutionContext}.
    * @param ec the gvien {@code ExecutionContext} provides access to
    *        local variables, arguments
    * @return the calculation results of the function
    */
   abstract Value invoke(final ExecutionContext ec);

   /**
    * Base class for functions that takes only one argument.
    */
   static abstract class F1 extends Invocable {
      protected final static String _arg_ = "!arg";   
      private final static String [] _arguments_ = {_arg_};
      
      @Override
      public String[] getArgumentNames() { return _arguments_; }
      
      public F1() {super("");}                              
      public F1(final String name) {super(name);}
   }
   
   /**
    * Base class for functions that takes two arguments.
    */
   static abstract class F2 extends Invocable {
      protected final static String _arg1_ = "!arg1"; 
      protected final static String _arg2_ = "!arg2"; 
      private final static String [] _arguments_ = {_arg1_, _arg2_};
     
      @Override
      public String[] getArgumentNames() { return _arguments_; }  
      
      public F2() {super("");}                               
      public F2(final String name) {super(name);}
   }
}
